﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
using KinectCore.Gui.Generator;
using xn;
using xnv;

namespace KinectCore.Gui.Controller
{
    public delegate void ControlHoverEvent(Control c);
    public delegate void ControlLeaveEvent(Control c);
    public delegate void ControlActivatedEvent(Control c);

    public class MouseEmulatorController
    {
        private readonly Color[] colors = { Color.Red, Color.Blue, Color.ForestGreen, Color.Yellow, Color.Orange, Color.Purple, Color.White };
        private readonly int ncolors = 6;


        // OpenNI components
        private Context context;
        private DepthGenerator depthGenerator;
        private HandsGenerator handsGenerator;
        private SessionManager sessionMgr;
        private Dictionary<uint, Point3D> handsList;

        // GUI components
        private Control _parentControl;
        private Dictionary<Rectangle, Control> activeControls = new Dictionary<Rectangle, Control>();
        private Control HoveringControl;

        // Temp vars used in high-efficiency methods
        private List<Control> tempControlList = new List<Control>();
        private float real_to_screen_ratio_x;
        private float real_to_screen_ratio_y;
        private float half_bitmap_x;
        private float half_bitmap_y;
        private Point screenPointOfHand;

        private bool shouldRun;
        private Thread readerThread;

        // property accessors
        public Control ParentControl
        {
            get { return _parentControl; }
            set
            {
                // remember the control
                _parentControl = value;

                lock (_parentControl)
                {
                    if (this.depthGenerator != null)
                    {
                        MapOutputMode mapMode = this.depthGenerator.GetMapOutputMode();

                        real_to_screen_ratio_x = (float)mapMode.nXRes / (float)_parentControl.Width;// / 1280f;
                        real_to_screen_ratio_y = (float)mapMode.nYRes / (float)_parentControl.Height;// / 960f;
                        half_bitmap_x = (float)mapMode.nXRes / 2f;
                        half_bitmap_y = (float)mapMode.nYRes / 2f;
                    }

                    // remove any active controls
                    lock (activeControls)
                    {
                        activeControls.Clear();
                    }

                    if (HoveringControl != null)
                    {
                        ControlLeave(HoveringControl);
                    }

                }

            }
        }
        
        public event ControlHoverEvent ControlHover;
        public event ControlActivatedEvent ControlActivated;
        public event ControlLeaveEvent ControlLeave;

        #region " Constructors / Destructors "
        public MouseEmulatorController(Context context)
        {
            // get our context and depth map generator
            this.context = context;
            this.activeControls = new Dictionary<Rectangle, Control>();


            // depth generator
            if (context != null)
                try
                {
                    this.depthGenerator = context.FindExistingNode(NodeType.Depth) as DepthGenerator;
                    this.depthGenerator.FieldOfViewChanged += new StateChangedHandler(depth_FieldOfViewChanged);
                }
                catch (XnStatusException ex)
                {
                    // TODO: do we need a better way to notify users?
                    Console.Error.WriteLine(ex.Message);
                }

            // create session manager
            if (context != null)
                try
                {
                    sessionMgr = new SessionManager(this.context, "Wave,RaiseHand", "RaiseHand");
                    sessionMgr.SessionStart += new SessionManager.SessionStartHandler(sm_SessionStart);
                }
                catch (XnStatusException ex)
                {
                    // TODO: do we need a better way to notify users?
                    Console.Error.WriteLine(ex.Message);
                }

            // hands generator
            this.handsList = new Dictionary<uint, Point3D>();
            if (context != null)
            {
                this.handsGenerator = new HandsGenerator(this.context);
                this.handsGenerator.HandCreate += new HandsGenerator.HandCreateHandler(handsGenerator_HandCreate);
                this.handsGenerator.HandDestroy += new HandsGenerator.HandDestroyHandler(handsGenerator_HandDestroy);
                this.handsGenerator.HandUpdate += new HandsGenerator.HandUpdateHandler(handsGenerator_HandUpdate);
            }

            this.shouldRun = true;
            this.readerThread = new Thread(ReaderThread);
            this.readerThread.Start();
        }

        #endregion
        #region " Kinect events "
        void depth_FieldOfViewChanged(ProductionNode node)
        {
            handsList.Clear();
            if (sessionMgr != null)
                sessionMgr.EndSession();

            // unhover control
            if (HoveringControl != null)
                ControlLeave(HoveringControl);


        }

        void sm_SessionStart(ref Point3D position)
        {
            // recapture the hand tracking working-vars (in case things have changed - it happens!!)
            CalcScalingRatios();

            if (this.handsGenerator != null)
                this.handsGenerator.StartTracking(ref position);
        }

        void handsGenerator_HandCreate(ProductionNode node, uint id, ref Point3D position, float fTime)
        {
            Console.WriteLine("Create hand " + id.ToString() + " at " + position.ToString());

            CalcScalingRatios();

            //I'll lock your hand on my list
            lock (handsList)
            {
                handsList.Add(id, position);
            }
            
        }

        void handsGenerator_HandDestroy(ProductionNode node, uint id, float fTime)
        {


            Console.WriteLine("Lost tracking for hand " + id.ToString());
            lock (handsList)
            {
                handsList.Remove(id);
            }

            if (sessionMgr != null)
                sessionMgr.EndSession();
        }

        void handsGenerator_HandUpdate(ProductionNode node, uint id, ref Point3D position, float fTime)
        {
            //Console.WriteLine("Updating hand " + id.ToString() + " to " + position.ToString());
            //lock (handsList)
            //{
            //    handsList.Remove(id);
            //    handsList.Add(id, position);
            //}

            //// check if we're over a button
            //try
            //{
            // PSK: removed on order of Stevie - new code seems to work better
            //RealPointToScreen(position, ref screenPointOfHand);
            screenPointOfHand.X = (int)(position.X + half_bitmap_x);
            screenPointOfHand.Y = (int)(-position.Y + half_bitmap_y);
            Cursor.Position = new Point(screenPointOfHand.X, screenPointOfHand.Y);

            //    tempControlList.Clear();

            //    foreach (Rectangle r in activeControls.Keys)
            //    {
            //        if (r.Contains(screenPointOfHand))
            //        {
            //            Control c = activeControls[r];

            //            // see if there's an existing hover, and it's not the same as this control
            //            if (HoveringControl != null && HoveringControl != c)
            //            {
            //                // if it is, unhover the previous control
            //                ControlLeave(HoveringControl);
            //            }

            //            // see if the control is a new hover
            //            if (HoveringControl != c)
            //            {
            //                HoveringControl = c;
            //                ControlHover(c);
            //            }
            //        }

            //        // TODO: activate controls
            //    }


            //}
            //catch (Exception e)
            //{
            //    Console.WriteLine(e.Message);
            //}


        }

        void userGenerator_NewUser(ProductionNode node, uint id)
        {
            //this.poseDetectionCapability.StartPoseDetection(this.calibPose, id);
        }

        void userGenerator_LostUser(ProductionNode node, uint id)
        {
            //this.joints.Remove(id);
        }
        #endregion

        private void CalcScalingRatios()
        {
            lock (_parentControl)
            {
                MapOutputMode mapMode = this.depthGenerator.GetMapOutputMode();
                real_to_screen_ratio_x = (float)mapMode.nXRes / (float)_parentControl.Width;// / 1280f;
                real_to_screen_ratio_y = (float)mapMode.nYRes / (float)_parentControl.Height;// / 960f;
                //half_bitmap_x = (float)mapMode.nXRes / 2f;
                //half_bitmap_y = (float)mapMode.nYRes / 2f;
                half_bitmap_x = (float)this._parentControl.Width / 2f;
                half_bitmap_y = (float)this._parentControl.Height / 2f;
            }
        }

        private void RealPointToScreen(Point3D hand, ref Point output)
        {
            float screen_x = (hand.X + half_bitmap_x) * real_to_screen_ratio_x;
            float screen_y = (-hand.Y + half_bitmap_y) * real_to_screen_ratio_y;
            output.X = (int)screen_x;
            output.Y = (int)screen_y;
        }

        public void AddControl(Control c)
        {
            Rectangle r = new Rectangle(c.Location, c.Size);

            lock (activeControls)
            {
                activeControls.Add(r, c);
            }
        }

        public void ClearControls()
        {
            lock (activeControls)
            {
                activeControls.Clear();
            }
        }


        private unsafe void ReaderThread()
        {
            DepthMetaData depthMD = new DepthMetaData();

            while (this.shouldRun)
            {
                try
                {
                    this.context.WaitOneUpdateAll(this.depthGenerator);
                }
                catch (Exception) { }

                if (this.depthGenerator != null)
                    this.depthGenerator.GetMetaData(depthMD);

            }

        }

        public void Start()
        {
            this.readerThread.Start();
        }

        public void Stop()
        {
            this.shouldRun = false;
            try
            {
                if (this.readerThread != null && this.readerThread.ThreadState != ThreadState.Unstarted)
                    this.readerThread.Join();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }

    }
}
